#include <icmp.h>

// static bool rtt = true;
char *hostname = NULL;
char hostaddr[128];
int sockfd;
struct sockaddr_in sock_addr;
int sendnum=0;
int recvnum=0;
char sendbuf[SENDBUF];
char recvbuf[RCVBUF];
struct timeval start_time;

pid_t pid;
double min = 0.0;
double avg = 0.0;
double max = 0.0;
double mdev = 0.0;

static void printHelp(char *prog);
static int icmp_get_options(int argc, char **argv);
static int icmp_getHostAddr();
static int icmp_sock_init();
static int icmp_signal();
static void handler(int sig);
static void icmp_send_package();
static void icmp_recv_package();
static void icmp_statistic();
static void icmp_close();


// 入口函数
void icmp_start(int argc, char **argv){
    // 初始化进程号
    pid = getpid();

    if(ICMP_OK != icmp_get_options(argc,argv)) {
        exit(ICMP_ERROR);
    }
    if(ICMP_OK != icmp_getHostAddr()) {
        exit(ICMP_ERROR);
    }

    if(ICMP_OK != icmp_sock_init()) {
        exit(ICMP_ERROR);
    }
    // 注册信号
    if(ICMP_OK != icmp_signal() ) {
        exit(ICMP_ERROR);
    }
    ICMP_BEGIN(hostname, hostaddr, icmp_package_size, icmp_package_size+28);
    gettimeofday(&start_time, 0);// 初始化开始时间
    icmp_send_package();
    icmp_recv_package();
    icmp_statistic();
    // 进行回收操作
    icmp_close();
}

/*
* 关闭处理
*/
void icmp_close() {
    close(sockfd);
    exit(ICMP_OK);
}
/* 
* 根据hostname获取主机地址
*/
static int icmp_getHostAddr() {
    if (!hostname) {
        printf("invalid host\n");
        return ICMP_ERROR;
    }
    struct hostent *host = gethostbyname(hostname);
    if(host == NULL) {
        printf("unknow host\n");
        return ICMP_ERROR;
    }
    if(host->h_name) {
        hostname = host->h_name;
    }
    if(host->h_addr_list != NULL && host->h_addr_list[0] != NULL) {
        
        if (NULL == inet_ntop(AF_INET, host->h_addr_list[0], hostaddr, sizeof(hostaddr))) {
            perror("inet_ntop:");
            return ICMP_ERROR;
        }
        memset(&sock_addr, 0, sizeof(sock_addr));
        sock_addr.sin_family = AF_INET;
        memcpy(&sock_addr.sin_addr, host->h_addr_list[0], 4);
    }
    
    return ICMP_OK;
}

/*
* 套接字初始化
*/
static int icmp_sock_init() {
    // 创建socket
    if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1){
        perror("socket");
        return ICMP_ERROR;
    }
    // 设置socket接收数据超时时间
    if ( -1 == setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&icmp_wait_timeout, sizeof(icmp_wait_timeout))){
        perror("setsockopt, timeout");
        return ICMP_ERROR;
    }

    // 设置ttl
    int ttl = icmp_ttl; 
    if (-1 == setsockopt(sockfd, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(ttl))) {
        perror("setsockopt, ttl");
        return ICMP_ERROR;
    }

    // if (-1 == setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (char *)&icmp_sndbuf_size, sizeof(icmp_sndbuf_size))) {
    //     perror("setsockopt,sndbuf");
    //     return ICMP_ERROR;
    // }

    return ICMP_OK;
}

/*
* 打印帮助
*/
static void printHelp(char *progName) {
    printf("welcome use %s program, version is v%s\n", progName, ICMP_VERSION);
    printf("usage:\t");
    printf("%s\t[-i wait] [-s packetsize] [-t ttl]\n\t\t[-c count] [-S sndbuf] [-w timeout] ", progName);
    printf("host\n");
}

/*
* 获取参数
*/
static int icmp_get_options(int argc, char **argv) {
    if (argc < 2) {
        printHelp(argv[0]);
        return ICMP_ERROR;
    }
    int ch,size,t;
    opterr = 1; // 将错误打开
    while((ch = getopt(argc, argv, short_opts)) != -1) {
        switch (ch) {
            case 'i':
                icmp_interval = atoi(optarg);
                if (!icmp_interval){
                    printf("-i must be integer\n");
                    return ICMP_ERROR;
                }
                break;
            case 's':
                size = atoi(optarg);
                if (size > ICMP_MAX_PACKET_SIZE) {
                    printf("%s package size overflow\n", argv[0]);
                    return ICMP_ERROR;
                }
                icmp_package_size = (uint16_t)size;
                break;
            case 't':
                t = atoi(optarg);
                if (t > IP_MAX_TTL) {
                    printf("%s TTL overflow\n", argv[0]);
                    return ICMP_ERROR;
                }
                icmp_ttl = (uint8_t)t;
                break;
            case 'c':
                icmp_count = atoi(optarg);
                break;
            case 'S':
                icmp_sndbuf_size = atoi(optarg);
                break;
            case 'w':
                icmp_wait_timeout.tv_sec = atoi(optarg);
                break;
            case '?':
                printHelp(argv[0]);
                return ICMP_ERROR;
        }
    }
    // 没有操作数情况
    if (optind == argc){
          printHelp(argv[0]);
          return ICMP_ERROR;  
    }
    hostname = argv[argc-1];
    return ICMP_OK;
}

/*
* 信号处理函数
*/
static void handler(int sig) {
    switch(sig){
        case SIGINT:
            // 统计信息
            icmp_statistic();
            icmp_close();
        break;
        case SIGALRM:
            // 发送报文
            icmp_send_package();
        break;
    }
}


static int icmp_signal() {
    struct sigaction act;
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0; 
    if (sigaction(SIGALRM, &act, NULL) == -1) {
        perror("signal");
        return ICMP_ERROR;
    }
    if (sigaction(SIGINT, &act, NULL) == -1) {
        perror("signal");
        return ICMP_ERROR;
    }
    return ICMP_OK;
}

/*
* 发送icmp报文
*/
static void icmp_send_package(){
    struct icmp *icmp_p = (struct icmp *)sendbuf;
    icmp_p->icmp_type = ICMP_ECHO;
    icmp_p->icmp_code = 0;
    icmp_p->icmp_cksum = 0;
    icmp_p->icmp_id = pid;
    icmp_p->icmp_seq = ++sendnum;
    createData((char *)icmp_p->icmp_data, icmp_package_size);
    icmp_p->icmp_cksum = my_chksum((uint16_t *)icmp_p, icmp_package_size + 8);
    int retval = sendto(sockfd, sendbuf , icmp_package_size + 8 , 0,(struct sockaddr *)&sock_addr, sizeof(sock_addr));
    if (retval == -1) {
        perror("send");
        exit(ICMP_ERROR);
    }
    icmp_count--;
    if (icmp_count) {
        alarm(icmp_interval);
    }
}

/*
* 接收icmp报文处理
*/
void icmp_recv_package() {
    int n = 0;
    socklen_t addrlen = sizeof(sock_addr);
    while(1) {
        n = recvfrom(sockfd, recvbuf, RCVBUF, 0, (struct sockaddr *)&sock_addr , &addrlen);
        if (n == -1) {
            // 如果是中断信号忽略
            if(errno == EINTR) {
                continue;
            } else if(errno == EAGAIN) {
                printf("Request timeout\n");
                return;
            } else {
                perror("recvfrom()");
                exit(ICMP_ERROR);
            }
        }
        // 
        struct ip *ip = (struct ip*)recvbuf;
        int iphrlen = (ip->ip_hl)<<2;
        struct icmp *icmp = (struct icmp*)(recvbuf + iphrlen);
        
        if((icmp->icmp_type != ICMP_ECHOREPLY)) {
            continue;
        }
        if(icmp->icmp_id != pid) {
            continue;
        }
        recvnum++;
        ICMP_RCVOUTPUT(n - iphrlen, hostaddr, icmp->icmp_seq, ip->ip_ttl);
        if ( icmp_package_size >= 16) {
            struct timeval *sndtime;
            sndtime = (struct timeval *)icmp->icmp_data;
            double rrt = mydifftime(sndtime);
        
            if (min > rrt || min == 0) {
                min = rrt;
            }
            if (max < rrt) {
                max = rrt;
            }
            avg += rrt;
            mdev += rrt * rrt;

            printf("time=%.3f ms", rrt);
        }
        printf("\n");
        if(sendnum == recvnum && !icmp_count) {
            break;
        }
    }
}
// 统计计算
void icmp_statistic(){
    double lt = ((float)sendnum-recvnum) / sendnum * 100;
    int losspercent = (int)lt;
    float pass_time = mydifftime(&start_time);

    printf("\n--- %s ping statistics ---\n", hostname);
    printf("%d packets transmitted, %d received, %d%% packet loss, time %.0fms\n", sendnum, recvnum, losspercent, pass_time);
    
    if (icmp_package_size >= 16 && recvnum > 0) {
        double tmp;
	    avg /= recvnum;
	    tmp = mdev / recvnum - avg * avg;
	    mdev = sqrt(tmp);

        printf("rtt min/avg/max/mdev = %.3f/%.3f/%.3f/%.3f ms\n", min, avg, max, mdev);
    }
}

